package com.faner.fast.util;

import ch.ethz.ssh2.*;
import com.faner.fast.exception.FailedException;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;

import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.Charset;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * 远程执行linux命令
 * description: ExecUtil
 * date: 2020/12/2 16:44
 * author: faner
 */
@Slf4j
public class ScpClientUtil {
    /** 超时时间 */
    private static final int TIME_OUT = 1000 * 5 * 60;

    static private Map<String,ScpClientUtil> instance = new HashMap<>();

    static synchronized public ScpClientUtil getInstance(String ip, int port, String username, String password) {
        if (instance.get(ip) == null) {
            instance.put(ip, new ScpClientUtil(ip, port, username, password));
        }
        return instance.get(ip);
    }

    public ScpClientUtil(String ip, int port, String username, String password) {
        this.ip = ip;
        this.port = port;
        this.username = username;
        this.password = password;
    }

    /**
     * 执行一个命令
     * @param scripts  需要执行的脚本
     * @param charset  字符编码
     * @return ShellResult类
     * @throws Exception
     */
    public ShellResult exec( String scripts, Charset charset) throws IOException {

        Connection conn = new Connection(ip, port);

        try {
            conn.connect();
            // Open a new {@link Session} on this connection
            boolean isAuthenticated = conn.authenticateWithPassword(username, password);
            if (!isAuthenticated) {
                log.error("authentication failed");
                throw new FailedException("authentication failed");
            }
            Session session = conn.openSession();
            InputStream stdOut = new StreamGobbler(session.getStdout()); InputStream stdErr = new StreamGobbler(session.getStderr());
            session.execCommand(scripts);
            String outStr = processStream(stdOut, charset.name());
            String outErr = processStream(stdErr, charset.name());
            session.waitForCondition(ChannelCondition.EXIT_STATUS, TIME_OUT);
            int exitStatus = session.getExitStatus();
            return new ShellResult(outStr, outErr, exitStatus);
        }finally{
            conn.close();
        }
    }

    /**
     * 执行脚本
     * @param in      输入流
     * @param charset 字符编码
     * @return
     * @throws IOException
     */
    private static String processStream(InputStream in, String charset) throws IOException {
        byte[] buf = new byte[1024];
        StringBuilder sb = new StringBuilder();
        while (in.read(buf) != -1) {
            sb.append(new String(buf, charset));
        }
        return sb.toString();
    }

    public void getFile(String remoteFile, String localTargetDirectory) {
        Connection conn = new Connection(ip, port);
        try {
            conn.connect();
            boolean isAuthenticated = conn.authenticateWithPassword(username, password);
            if (!isAuthenticated) {
                System.err.println("authentication failed");
            }
            SCPClient client = new SCPClient(conn);
            client.get(remoteFile, localTargetDirectory);
        } catch (IOException ex) {
            Logger.getLogger(SCPClient.class.getName()).log(Level.SEVERE, null, ex);
        }finally{
            conn.close();
        }
    }

    public void putFile(String localFile, String remoteTargetDirectory) {
        putFile(localFile, null, remoteTargetDirectory);
    }

    public void putFile(String localFile, String remoteFileName, String remoteTargetDirectory) {
        putFile(localFile, remoteFileName, remoteTargetDirectory,null);
    }

    public void putFile(String localFile, String remoteFileName, String remoteTargetDirectory, String mode) {
        Connection conn = new Connection(ip, port);
        try {
            conn.connect();
            boolean isAuthenticated = conn.authenticateWithPassword(username, password);
            if (!isAuthenticated) {
                System.err.println("authentication failed");
            }
            SCPClient client = new SCPClient(conn);
            if ((mode == null) || (mode.length() == 0)) {
                mode = "0600";
            }
            if (remoteFileName == null) {
                client.put(localFile, remoteTargetDirectory);
            } else {
                client.put(localFile, remoteFileName, remoteTargetDirectory, mode);
            }
        } catch (IOException ex) {
            Logger.getLogger(ScpClientUtil.class.getName()).log(Level.SEVERE, null, ex);
        }finally{
            conn.close();
        }
    }

    private String ip;
    private int port;
    private String username;
    private String password;


    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    public  class ShellResult  {

        /** 脚本输出结果 */
        private String result;
        /** 异常输出结果 */
        private String errorMsg;
        /** 回话退出状态 */
        private int exitStatus;

        /** 是否成功关闭会话 */
        public boolean getSuccess() {
            return this.exitStatus == 0;
        }
    }
}
